import binascii
import sys

'''
Date: 2020-07-02
Author: VMware Carbon Black TAU - Scott Knight
Exemplar Hashes: 365a5c72f52de964b8dc134d2fc45f9c73ba045cebd9fd397b1e26fdb11bfec6, 5a024ffabefa6082031dccdb1e74a7fec9f60f257cd0b1ab0f698ba2a5baca6b, d18daea336889f5d7c8bd16a4d6358ddb315766fa21751db7d41f0839081aee2, 06974e23a3bf303f75c754156f36f57b960f0df79a38407dfdef9a1c55bf8bff
Description: Decodes encoded strings from OSX.ThiefQuest malware
'''

__VERSION__ = '1.0'

STR_FA = b'\x0D\x00\x00\x00\x00\x00\x00\x00\xA4\x06\xA3\x02\x9C\x0B\x4F\x00\x00\x00\x00\x00\x10\x29\xD4\x35\xB6\xD3\x5A\x63\x61\xDD\x4F\x54\x67\xCA\xFB\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
STR_KEY = 0x6B8B4567
STR_FA_NEW = b'\x80\x00\x00\x00\x00\x00\x00\x00\x88\x00\xA6\x0D\x40\xBD\x5C\x00\x00\x00\x00\x00\x3B\x1B\x7F\x7E\xE5\x3E\xCA\x66\x74\xBC\x40\x5B\x76\x5D\x2D\x28\xB2\x4C\x9B\x83\x43\x06\xC1\x2D\x50\xA6\x3F\x97\x5D\xC9\xA5\xCC\x3D\x25\x50\x7D\x40\xC2\x7E\xA9\x66\x78\xF4\x72\x39\x11\x42\x06\xC3\xD5\x47\x93\x0C\x65\x1D\xE8\xE4\x76\xB7\x21\xE9\x34\x96\xD3\x35\x65\x3B\x50\xCF\x55\xA6\x24\x78\x49\x4E\xFF\x1C\x31\x35\x42\xBE\x80\xF2\x04\xEA\x02\x77\x23\x2D\xA2\x61\xBC\xC7\x90\x63\x96\x50\x29\x64\x38\x32\xF0\x59\xA9\xC2\x66\x65\x46\x6F\x1F\x03\x32\x7A\xA5\xDE\xD2\x3F\xD5\x2C\x6E\xB6\x29\xE0\x76\xC7\xE2\xC7\x36\x48\x23\x4D\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
STR_KEY_NEW = 0x7D47950
DESERIALIZE_KEY = b'NCUCKOO7614S'
SPOT_KEY = b'H2QGjSmA'
LOOKUP = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\x00\x00\x00\x00\x00\x00\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x20\x21\x22\x23\x00\x00\x00\x00\x00\x00\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F'
LOOKUP2 = b'\xD9\x78\xF9\xC4\x19\xDD\xB5\xED\x28\xE9\xFD\x79\x4A\xA0\xD8\x9D\xC6\x7E\x37\x83\x2B\x76\x53\x8E\x62\x4C\x64\x88\x44\x8B\xFB\xA2\x17\x9A\x59\xF5\x87\xB3\x4F\x13\x61\x45\x6D\x8D\x09\x81\x7D\x32\xBD\xC9\x40\xEB\x86\xB7\x7B\x0B\xF0\x95\x21\x22\x5C\x6B\x4E\x82\x54\xD6\x65\x93\xCE\x60\xB2\x1C\x73\x56\x71\x14\xA7\x8C\xF1\xDC\x12\x75\xCA\x1F\x3B\xBE\xE4\xD1\x42\x3D\xD4\x30\xA3\x3C\xB6\x26\x6F\xBF\x0E\xDA\x46\x69\x07\x57\x27\xF2\xD2\x9B\xBC\x94\x43\x03\xF8\x11\x6C\xF6\x90\xEF\x3E\xE7\x06\xC3\xD5\x2F\xC8\x66\x1E\xD7\x08\xE8\xEA\xDE\x80\x52\xEE\xF7\x84\xAA\x72\xAC\x35\x4D\x6A\x2A\x96\x1A\x1D\xC0\x5A\x15\x49\x74\x4B\x9F\xD0\x5E\x04\x18\xA4\xEC\xC2\xE0\x41\x6E\x0F\x51\xCB\xCC\x24\x91\xAF\x50\xA1\xF4\x70\x39\x99\x7C\x3A\x85\x23\xB8\xB4\x7A\xFC\x02\x36\x5B\x25\x55\x97\x31\x2D\x5D\xFA\x98\xE3\x8A\x92\xAE\x05\xDF\x29\x10\x67\xC7\xBA\x8F\xD3\x00\xE6\xCF\xE1\x9E\xA8\x2C\x63\x16\x01\x3F\x58\xE2\x89\xA9\x0D\x38\x34\x1B\xAB\x33\xFF\xB0\xBB\x7F\x0C\x5F\xB9\xB1\xCD\x2E\xC5\xF3\xDB\x47\xE5\xA5\x9C\x77\x0A\xA6\x20\x68\xFE\x48\xC1\xAD'
LOOKUP3 = b'\x02\x03\x05\x07\x0B\x0D\x11\x13\x17\x1D\x1F\x25\x29\x2B\x2F\x35\x3B\x3D\x43\x47\x49\x4F\x53\x59\x61\x65\x67\x6B\x6D\x71\x7F\x83'

# Rotate left: 0b1001 --> 0b0011
rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
 
# Rotate right: 0b1001 --> 0b1100
ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

def eip_key(key1, key2, key3):
  v6 = 1
  v3 = key1 % key3
  v5 = v3
  if not v3:
    return 0

  while key2:
    if (key2 & 1) == 1:
      v6 = v5 * v6 % key3
    key2 >>= 1
    v5 = v5 * v5 % key3

  return v6

def eip_decrypt(data, key):
    v9 = int.from_bytes(data[0:8], byteorder='little')
    v13 = int.from_bytes(data[8:12], byteorder='little')
    v14 = int.from_bytes(data[12:20], byteorder='little')

    key = eip_key(v14, key, v13)

    v7 = 4 - v9 % 4 + v9
    count = v7 // 4

    buf = data[20:20+v7]

    v4 = 0
    output = bytearray()
    while v4 < count:
        val = int.from_bytes(buf[v4*4:v4*4 + 4], byteorder='little')
        val = val ^ key
        output += val.to_bytes(4, byteorder='little')
        key = rol(key, 1, 32)
        v4 += 1

    return output[0:v9]

def eib_decode(input, input_len):
    size = input_len - 1
    v7 = (input_len - 1) // 6
    output_len = (4 * v7) - (ord(input[-1]) - 48)

    if output_len > input_len - 1:
        return ''

    v4 = 0
    output = bytearray()
    while v4 < v7:
        data = input[v4*6:v4*6+6]
        val = eib_unpack_i(data)
        output += val.to_bytes(4, byteorder='little')
        v4 += 1

    return output[0:output_len]

def eib_unpack_i(data):
    v3 = 0

    for i in range(0, 6):
        v3 += LOOKUP[ord(data[5-i]) - 48] << (6 * i)
    
    return v3

def eib_secure_decode(input, input_len, key):
    buf = eib_decode(input, input_len)
    output = tpdcrypt(key, buf, len(buf))
   
    return output

def tpdcrypt(key, input, input_len):
    size = input_len - 1    
    output_len = (input_len - 1) - input[-1]
    
    v7 = 0
    v6 = 2
    output = bytearray()
    derived_key =  bytearray(b'\x00' * 128)

    while v7 < size:
        derived_key = generate_xkey(derived_key, key, 0x400, v6)
        output += tp_decrypt(derived_key, input[v7:v7+8])
        v7 += 8
        v6 += 1

    return output[0:output_len]

def generate_xkey(derived_key, key, bits, block):
    # The malware uses strlen and the new key has a null byte
    # so we need to actually scan for a null byte
    #key_len = len(key)
    key_len = 0
    for i in range(0, len(key)):
        if key[i] == 0x00:
            break
        key_len += 1

    dst = bytearray(LOOKUP2[0:0x100])

    if block >= 0x20:
        block = block & 0x1f

    v19 = LOOKUP3[block]

    for i in range(0, 256, v19):
        dst[i] = (dst[i] % v19 + dst[i]) % 255

    if key_len > 128:
        key = key[0:128]
        key_len = 128

    if bits is None:
        bits = 1024

    derived_key[0:key_len] = key[0:key_len]

    if key_len < 128:
        v13 = 0
        v16 = derived_key[key_len-1]

        while True:
            v4 = v13
            v13 += 1
            idx = (derived_key[v4] + v16) & 0xff
            v16 = dst[idx]
            v5 = key_len
            key_len += 1
            derived_key[v5] = v16

            if key_len >= 128:
                break    

    v1 = (bits + 7) >> 3
    v14 = 128 - v1     
    v17 = dst[(255 >> (-bits & 7)) & derived_key[128 - v1]]
    derived_key[128-v1] = v17
 
    while v14 > 0:
        v14 -= 1
        v17 = dst[(derived_key[v1+v14] ^ v17) & 0xff]
        derived_key[v14] = v17    
    
    # NOTE: This shows in the decompilation but seems to just be setting
    # all the values in the derived_key to the value they already have.
    # Maybe just junk instructions?
    v15 = 63
    while True: 
        derived_key[v15*2]   = derived_key[v15*2]
        derived_key[v15*2+1] = derived_key[(v15*2) + 1]
        if v15 <= 0:
            break
        v15 -= 1

    return derived_key

def tp_decrypt(key, input): 
    output = bytearray()
    v9 = int.from_bytes(input[6:8], byteorder='little')
    v8 = int.from_bytes(input[4:6], byteorder='little')
    v7 = int.from_bytes(input[2:4], byteorder='little')
    v6 = int.from_bytes(input[0:2], byteorder='little')
    v5 = 15

    while True:
        v9 = v9 & 0xffff
        v9 = ((v9 >> 5) + (v9 << 11)) - (((v8 & v7) + (~v8 & v6)) + int.from_bytes(key[(2*(4*v5+3)):(2*(4*v5+3))+2], byteorder='little'))

        v8 = v8 & 0xffff
        v8 = ((v8 >> 3) + (v8 << 13)) - (((v7 & v6) + (~v7 & v9)) + int.from_bytes(key[(2*(4*v5+2)):(2*(4*v5+2))+2], byteorder='little'))

        v7 = v7 & 0xffff
        v7 = ((v7 >> 2) + (v7 << 14)) - (((v6 & v9) + (~v6 & v8)) + int.from_bytes(key[(2*(4*v5+1)):(2*(4*v5+1))+2], byteorder='little'))

        v6 = v6 & 0xffff
        v6 = ((v6 >> 1) + (v6 << 15)) - (((v9 & v8) + (~v9 & v7)) + int.from_bytes(key[(2*(4*v5+0)):(2*(4*v5+0))+2], byteorder='little'))

        if v5 == 5 or v5 == 11:
            v9 -= int.from_bytes(key[(2*(v8 & 0x3f)):(2*(v8 & 0x3f))+2], byteorder='little')
            v8 -= int.from_bytes(key[(2*(v7 & 0x3f)):(2*(v7 & 0x3f))+2], byteorder='little')
            v7 -= int.from_bytes(key[(2*(v6 & 0x3f)):(2*(v6 & 0x3f))+2], byteorder='little')
            v6 -= int.from_bytes(key[(2*(v9 & 0x3f)):(2*(v9 & 0x3f))+2], byteorder='little')

        if v5 <= 0:
            break
        v5 -=1

    v9 = v9 & 0xffff
    v8 = v8 & 0xffff
    v7 = v7 & 0xffff
    v6 = v6 & 0xffff

    output += v6.to_bytes(2, byteorder='little')
    output += v7.to_bytes(2, byteorder='little')
    output += v8.to_bytes(2, byteorder='little')
    output += v9.to_bytes(2, byteorder='little')

    return output

def ei_str(encrypted, data, key):
    eib_string_key = eip_decrypt(data, key)
    encrypted_len = len(encrypted)    
    output = eib_secure_decode(encrypted, encrypted_len, eib_string_key)
    if output != '':
        return output
    else:
        return encrypted

def usage():
    print('OSX.ThiefQuest Decryption Tool v%s\n' % __VERSION__)
    print('Usage:\n  %s [string|stringnew|network|spot] <encrypted string>' % (sys.argv[0]))
    sys.exit(1)

def main():
    if len(sys.argv) != 3:
        usage()

    kind = sys.argv[1]
    if kind not in ('string', 'stringnew', 'network', 'spot'):
        usage()

    encrypted = sys.argv[2]

    if kind == 'string':
        decrypted = ei_str(encrypted, STR_FA, STR_KEY)
    elif kind == 'stringnew':
        decrypted = ei_str(encrypted, STR_FA_NEW, STR_KEY_NEW)
    elif kind == 'network':
        key = ei_str('3cILm620z4sP2wLtV80WU3qG0000033', STR_FA, STR_KEY)[0:-1]
        decrypted = eib_secure_decode(encrypted, len(encrypted), key)
    elif kind == 'spot':
        key = ei_str('217{Z301RT8X3r7eIZ0{99WS0000073', STR_FA, STR_KEY)[0:-1]
        decrypted = eib_secure_decode(encrypted, len(encrypted), key)
    
    print(decrypted)

if __name__ == "__main__":
    main()
